package org.magicsoft.office.excel.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.magicsoft.office.db.DatabaseType;
import org.magicsoft.office.exception.ModelException;

public class Sheet {

	private String sheetName = null;

	private List<TargetTable> targetTables = null;

	private Map<String, Column> columns = null;

	private List<Reference> references = null;

	private Column firstColumn = null;

	private Column lastColumn = null;

	private int excelRowParameterIndex = -1;

	private int errorMsgParameterIndex = -1;

	private String tempTableName = null;

	private static final Pattern cp = Pattern.compile("[A-Z]+");

	public Sheet(List<TargetTable> targetTables, List<Column> columns) {
		this(null, targetTables, columns);
	}

	public Sheet(String sheetName, List<TargetTable> targetTables, List<Column> columns) {
		this.sheetName = sheetName;
		this.targetTables = targetTables;
		this.setColumns(columns);
		this.completeTargetTable(targetTables);
	}

	public void initTempTableName(DatabaseType databaseType, boolean useTemporaryTable) {
		String masterTargetTable = this.getTargetTables().get(0).getTableName();
		if (masterTargetTable.length() > 20) {
			masterTargetTable = masterTargetTable.substring(0, 20);
		}
		this.tempTableName = masterTargetTable + "_" + Integer.toHexString(new Random().nextInt(Integer.MAX_VALUE));
		if (DatabaseType.SQLSERVER.equals(databaseType) && useTemporaryTable) {
			this.tempTableName = "#" + tempTableName;
		}
	}

	public String getSheetName() {
		return sheetName;
	}

	public void setSheetName(String sheetName) {
		this.sheetName = sheetName;
	}

	public List<TargetTable> getTargetTables() {
		return targetTables;
	}

	private void completeTargetTable(List<TargetTable> targetTables) {
		for (TargetTable targetTable : targetTables) {
			if (targetTable.getUniqueKeys() == null) {
				continue;
			}

			for (UniqueKey uniqueKey : targetTable.getUniqueKeys()) {
				this.setUniqueKeyDBColumns(targetTable, uniqueKey);
			}
		}
	}

	private void setUniqueKeyDBColumns(TargetTable targetTable, UniqueKey uniqueKey) {
		String bindColumn;
		for (String column : uniqueKey.getColumns()) {
			if (!this.getColumns().containsKey(column)) {
				throw new ModelException("目标表：“" + targetTable.getDisplayName() + "”中声明的唯一键：“" + uniqueKey.getColumns()
						+ "”对应的Excel列没有在列模型中找到，请检查您的模型。");
			}

			bindColumn = this.getColumns().get(column).getBindColumn();
			if (bindColumn == null) {
				throw new ModelException("目标表：“" + targetTable.getDisplayName() + "”中声明的唯一键：“" + uniqueKey.getColumns()
						+ "”对应的Excel列没有在模型中绑定数据库字段，请修改您的模型。");
			}
			uniqueKey.getDbColumns().add(this.getColumns().get(column).getBindColumn());
		}
	}

	public Map<String, Column> getColumns() {
		return columns;
	}

	public Column getColumn(String cell) {
		Matcher m = cp.matcher(cell);
		if (m.find()) {
			return this.columns.get(m.group(0));
		} else {
			return null;
		}
	}

	private void setColumns(List<Column> columns) {
		this.columns = new LinkedHashMap<>();

		int i = 0;
		Column prev = null;
		ColumnCheckContext columnCheckContext = new ColumnCheckContext();
		for (Column column : columns) {
			this.checkColumnModel(column, columnCheckContext);

			if (i++ == 0) {
				this.firstColumn = column;
			}
			this.lastColumn = column;
			column.setPrev(prev);
			if (prev != null) {
				prev.setNext(column);
			}
			prev = column;

			this.completeColumn(column, i);
			this.columns.put(column.getColumnNumber(), column);
		}

		this.completeReferences();
	}

	/**
	 * 列模型检查。
	 * 
	 * @param column
	 *            列实例。
	 * @param columnCheckContext
	 *            列模型检查上下文。（此方法是为了解决代码质量检查分析提出来的，之前都是局部变量，现在只能用一个上下文对象承载那些变量了。）
	 */
	private void checkColumnModel(Column column, ColumnCheckContext columnCheckContext) {
		if (column instanceof ExtColumn) {
			columnCheckContext.hasExtColumn = true;
		} else {
			if (columnCheckContext.hasExtColumn) {
				throw new ModelException("扩展列（ExtColumn）必须出现在列模型集合的最后，请检查您的配置。");
			}

			if (!columnCheckContext.columnSet.contains(column.getColumnNumber())) {
				columnCheckContext.columnSet.add(column.getColumnNumber());
			} else {
				throw new ModelException("列号：“" + column.getColumnNumber() + "”在模型中已经出现过，请检查您的配置。");
			}
			columnCheckContext.curColumnIndex = this.getColumnIndex(column.getColumnNumber());
			if (columnCheckContext.curColumnIndex > columnCheckContext.maxColumnIndex) {
				columnCheckContext.maxColumnIndex = columnCheckContext.curColumnIndex;
			} else {
				throw new ModelException("列号：“" + column.getColumnNumber() + "”在模型中的位置异常，在前面检测到比它位置更靠后的列。");
			}
		}
	}

	/**
	 * 对列模型进行一些补全操作。
	 * 
	 * @param column
	 *            列模型实例。
	 * @param i
	 *            当前索引。
	 */
	private void completeColumn(Column column, int i) {
		column.setParameterIndex(i); // 这里没用i+1，因为上面已经自加过了。
		if (column.getBindTable() == null && this.targetTables.size() == 1) {
			column.setBindTable(this.targetTables.get(0));
		} else {
			for (TargetTable targetTable : this.targetTables) {
				if (targetTable.getTableName().equals(column.getBindTable().getTableName())) {
					column.setBindTable(targetTable);
					break;
				}
			}
		}
		if (column.getReference() != null) {
			this.getReferences().add(column.getReference());
		}
	}

	private void completeReferences() {
		boolean foundMappingColumn;
		for (Reference reference : this.getReferences()) {
			if (reference.getRefTableName().equals(reference.getColumn().getBindTable().getTableName())) {
				foundMappingColumn = false;
				for (Map.Entry<String, Column> entry : this.columns.entrySet()) {
					if (reference.getRefTableName().equals(entry.getValue().getBindTable().getTableName())
							&& reference.getRefDisplayColumn().equals(entry.getValue().getBindColumn())) {
						reference.setMappingColumn(entry.getValue());
						foundMappingColumn = true;
						break;
					}
				}

				if (!foundMappingColumn) {
					throw new ModelException("对于自关联的数据，如果要更新上级的ID，则必须将更新的依据也作为一列放置到excel中。");
				}
			}
		}
	}

	public List<Reference> getReferences() {
		if (this.references == null) {
			this.references = new ArrayList<>();
		}
		return references;
	}

	public void setReferences(List<Reference> references) {
		this.references = references;
	}

	public Column getFirstColumn() {
		return firstColumn;
	}

	public void setFirstColumn(Column firstColumn) {
		this.firstColumn = firstColumn;
	}

	public Column getLastColumn() {
		return lastColumn;
	}

	public void setLastColumn(Column lastColumn) {
		this.lastColumn = lastColumn;
	}

	public int getExcelRowParameterIndex() {
		return excelRowParameterIndex;
	}

	public void setExcelRowParameterIndex(int excelRowParameterIndex) {
		this.excelRowParameterIndex = excelRowParameterIndex;
	}

	public int getErrorMsgParameterIndex() {
		return errorMsgParameterIndex;
	}

	public void setErrorMsgParameterIndex(int errorMsgParameterIndex) {
		this.errorMsgParameterIndex = errorMsgParameterIndex;
	}

	public String getTempTableName() {
		return tempTableName;
	}

	public void setTempTableName(String tempTableName) {
		this.tempTableName = tempTableName;
	}

	private int getColumnIndex(String column) {
		if (!column.matches("[A-Z]+")) {
			throw new ModelException("列号：“" + column + "”不是合法的excel列号。");
		}
		int index = 0;
		char[] chars = column.toUpperCase().toCharArray();
		for (int i = 0; i < chars.length; i++) {
			index += ((int) chars[i] - (int) 'A' + 1) * Math.pow(26, chars.length - i - 1);
		}
		return index;
	}

	private class ColumnCheckContext {

		private int curColumnIndex = -1;

		private int maxColumnIndex = -1;

		private boolean hasExtColumn = false;

		private Set<String> columnSet = new HashSet<>();

	}

}
